/*
 *  Copyright (C) 2011-2013 JPEXS
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


package com.jpexs.asdec.abc.methodinfo_parser;

import com.jpexs.asdec.Main;
import com.jpexs.asdec.abc.ABC;
import com.jpexs.asdec.abc.types.MethodInfo;
import com.jpexs.asdec.abc.types.ValueKind;
import com.jpexs.asdec.abc.types.traits.TraitSlotConst;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author JPEXS
 */
public class MethodInfoParser {

   public static boolean parseSlotConst(String text, TraitSlotConst trait, ABC abc) throws ParseException {
      MethodInfoLexer lexer = new MethodInfoLexer(new ByteArrayInputStream(text.getBytes()));
      ParsedSymbol symb;
      int type_index = -1;
      ValueKind value = new ValueKind(0, 0);
      try {
         ParsedSymbol symbType = lexer.yylex();
         if (symbType.type == ParsedSymbol.TYPE_STAR) {
            type_index = 0;
         } else if (symbType.type == ParsedSymbol.TYPE_MULTINAME) {
            type_index = (int) (long) (Long) symbType.value;
         } else {
            throw new ParseException("Multiname or * expected", lexer.yyline());
         }
         ParsedSymbol symbEqual = lexer.yylex();
         if (symbEqual.type == ParsedSymbol.TYPE_ASSIGN) {
            ParsedSymbol symbValue;
            String nstype = "";
            do {
               symbValue = lexer.yylex();
               if (symbValue.type >= 8 && symbValue.type <= 13) {
                  nstype = nstype + symbValue.type + ":";
               }
            } while (symbValue.type >= 8 && symbValue.type <= 13);
            if ((!nstype.equals("")) && (symbValue.type != ParsedSymbol.TYPE_NAMESPACE)) {
               throw new ParseException("Namespace expected", lexer.yyline());
            }
            int id = 0;
            switch (symbValue.type) {
               case ParsedSymbol.TYPE_INTEGER:
                  value = new ValueKind(abc.constants.forceGetIntId((Long) symbValue.value), ValueKind.CONSTANT_Int);
                  break;
               case ParsedSymbol.TYPE_FLOAT:
                  value = new ValueKind(abc.constants.forceGetDoubleId((Double) symbValue.value), ValueKind.CONSTANT_Double);
                  break;
               case ParsedSymbol.TYPE_STRING:
                  value = new ValueKind(abc.constants.forceGetStringId((String) symbValue.value), ValueKind.CONSTANT_Utf8);
                  break;
               case ParsedSymbol.TYPE_TRUE:
                  value = new ValueKind(0, ValueKind.CONSTANT_True);
                  break;
               case ParsedSymbol.TYPE_FALSE:
                  value = new ValueKind(0, ValueKind.CONSTANT_False);
                  break;
               case ParsedSymbol.TYPE_NULL:
                  value = new ValueKind(0, ValueKind.CONSTANT_Null);
                  break;
               case ParsedSymbol.TYPE_UNDEFINED:
                  value = new ValueKind(0, ValueKind.CONSTANT_Undefined);
                  break;
               case ParsedSymbol.TYPE_NAMESPACE:
                  if (nstype.equals("9:")) {
                     value = new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_PackageNamespace);
                  } else if (nstype.equals("9:10:")) {
                     value = new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_PackageInternalNs);
                  } else if (nstype.equals("13:")) {
                     value = new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_ProtectedNamespace);
                  } else if (nstype.equals("12:")) {
                     value = new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_ExplicitNamespace);
                  } else if (nstype.equals("11:13:")) {
                     value = new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_StaticProtectedNs);
                  } else if (nstype.equals("8:")) {
                     value = new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_PrivateNs);
                  } else if (nstype.equals("")) {
                     value = new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_Namespace);
                  } else {
                     throw new ParseException("Invalid type of namespace", lexer.yyline());
                  }
                  break;
               default:
                  throw new ParseException("Unexpected symbol", lexer.yyline());
            }
            symb = lexer.yylex();
            if (symb.type != ParsedSymbol.TYPE_EOF) {
               throw new ParseException("Unexpected symbol", lexer.yyline());
            }
         } else if (symbEqual.type == ParsedSymbol.TYPE_EOF) {
         } else {
            throw new ParseException("Unexpected symbol", lexer.yyline());
         }
      } catch (IOException ex) {
         return false;
      }
      trait.type_index = type_index;
      trait.value_kind = value.value_kind;
      trait.value_index = value.value_index;
      return true;
   }

   public static boolean parseReturnType(String text, MethodInfo update) throws ParseException {
      MethodInfoLexer lexer = new MethodInfoLexer(new ByteArrayInputStream(text.getBytes()));
      ParsedSymbol symb;
      int type = -1;
      try {
         symb = lexer.yylex();
         if (symb.type == ParsedSymbol.TYPE_STAR) {
            type = 0;
         } else if (symb.type == ParsedSymbol.TYPE_MULTINAME) {
            type = (int) (long) (Long) symb.value;
         } else {
            throw new ParseException("Multiname or * expected", lexer.yyline());
         }
         symb = lexer.yylex();
         if (symb.type != ParsedSymbol.TYPE_EOF) {
            throw new ParseException("Only one return type allowed", lexer.yyline());
         }
         update.ret_type = type;
         return true;
      } catch (IOException ex) {
      }
      return false;
   }

   public static boolean parseParams(String text, MethodInfo update, ABC abc) throws ParseException {
      MethodInfoLexer lexer = new MethodInfoLexer(new ByteArrayInputStream(text.getBytes()));
      List<String> paramNames = new ArrayList<String>();
      List<Long> paramTypes = new ArrayList<Long>();
      List<ValueKind> optionalValues = new ArrayList<ValueKind>();
      boolean hasOptional = false;
      boolean needsRest = false;
      try {
         ParsedSymbol symb;
         symb = lexer.yylex();
         while (symb.type != ParsedSymbol.TYPE_EOF) {
            if (symb.type == ParsedSymbol.TYPE_DOTS) {
               needsRest = true;
               symb = lexer.yylex();
               if (symb.type != ParsedSymbol.TYPE_IDENTIFIER) {
                  throw new ParseException("Identifier expected", lexer.yyline());
               }
               symb = lexer.yylex();
               if (symb.type != ParsedSymbol.TYPE_EOF) {
                  throw new ParseException("End expected after rest params", lexer.yyline());
               }
               break;
            }

            if (symb.type != ParsedSymbol.TYPE_IDENTIFIER) {
               throw new ParseException("Identifier expected", lexer.yyline());
            }
            paramNames.add((String) symb.value);
            symb = lexer.yylex();
            if (symb.type == ParsedSymbol.TYPE_COLON) {
               ParsedSymbol symbType = lexer.yylex();
               if (symbType.type == ParsedSymbol.TYPE_STAR) {
                  paramTypes.add(new Long(0));
               } else if (symbType.type == ParsedSymbol.TYPE_MULTINAME) {
                  paramTypes.add((Long) symbType.value);
               } else {
                  throw new ParseException("Multiname or * expected", lexer.yyline());
               }
               ParsedSymbol symbEqual = lexer.yylex();
               if (symbEqual.type == ParsedSymbol.TYPE_ASSIGN) {
                  hasOptional = true;
                  ParsedSymbol symbValue;
                  String nstype = "";
                  do {
                     symbValue = lexer.yylex();
                     if (symbValue.type >= 8 && symbValue.type <= 13) {
                        nstype = nstype + symbValue.type + ":";
                     }
                  } while (symbValue.type >= 8 && symbValue.type <= 13);
                  if ((!nstype.equals("")) && (symbValue.type != ParsedSymbol.TYPE_NAMESPACE)) {
                     throw new ParseException("Namespace expected", lexer.yyline());
                  }
                  int id = 0;
                  switch (symbValue.type) {
                     case ParsedSymbol.TYPE_INTEGER:
                        optionalValues.add(new ValueKind(abc.constants.forceGetIntId((Long) symbValue.value), ValueKind.CONSTANT_Int));
                        break;
                     case ParsedSymbol.TYPE_FLOAT:
                        optionalValues.add(new ValueKind(abc.constants.forceGetDoubleId((Double) symbValue.value), ValueKind.CONSTANT_Double));
                        break;
                     case ParsedSymbol.TYPE_STRING:
                        optionalValues.add(new ValueKind(abc.constants.forceGetStringId((String) symbValue.value), ValueKind.CONSTANT_Utf8));
                        break;
                     case ParsedSymbol.TYPE_TRUE:
                        optionalValues.add(new ValueKind(0, ValueKind.CONSTANT_True));
                        break;
                     case ParsedSymbol.TYPE_FALSE:
                        optionalValues.add(new ValueKind(0, ValueKind.CONSTANT_False));
                        break;
                     case ParsedSymbol.TYPE_NULL:
                        optionalValues.add(new ValueKind(0, ValueKind.CONSTANT_Null));
                        break;
                     case ParsedSymbol.TYPE_UNDEFINED:
                        optionalValues.add(new ValueKind(0, ValueKind.CONSTANT_Undefined));
                        break;
                     case ParsedSymbol.TYPE_NAMESPACE:
                        if (nstype.equals("9:")) {
                           optionalValues.add(new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_PackageNamespace));
                        } else if (nstype.equals("9:10:")) {
                           optionalValues.add(new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_PackageInternalNs));
                        } else if (nstype.equals("13:")) {
                           optionalValues.add(new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_ProtectedNamespace));
                        } else if (nstype.equals("12:")) {
                           optionalValues.add(new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_ExplicitNamespace));
                        } else if (nstype.equals("11:13:")) {
                           optionalValues.add(new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_StaticProtectedNs));
                        } else if (nstype.equals("8:")) {
                           optionalValues.add(new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_PrivateNs));
                        } else if (nstype.equals("")) {
                           optionalValues.add(new ValueKind((int) (long) (Long) symbValue.value, ValueKind.CONSTANT_Namespace));
                        } else {
                           throw new ParseException("Invalid type of namespace", lexer.yyline());
                        }
                        break;
                     default:
                        throw new ParseException("Unexpected symbol", lexer.yyline());
                  }
                  symb = lexer.yylex();
                  if (symb.type == ParsedSymbol.TYPE_COMMA) {
                  } else if (symb.type == ParsedSymbol.TYPE_EOF) {
                     break;
                  }
               } else if (symbEqual.type == ParsedSymbol.TYPE_COMMA) {
                  if (hasOptional) {
                     throw new ParseException("Parameter must have default value", lexer.yyline());
                  }
               } else if (symbEqual.type == ParsedSymbol.TYPE_EOF) {
                  if (hasOptional) {
                     throw new ParseException("Parameter must have default value", lexer.yyline());
                  }
                  break;
               } else {
                  throw new ParseException("Unexpected symbol", lexer.yyline());
               }
            } else if (symb.type == ParsedSymbol.TYPE_COMMA) {
            } else if (symb.type == ParsedSymbol.TYPE_EOF) {
               break;
            } else {
               throw new ParseException("Unexpected symbol", lexer.yyline());
            }
            symb = lexer.yylex();
         }
      } catch (IOException iex) {
         return false;
      }

      if (needsRest && (!optionalValues.isEmpty())) {
         throw new ParseException("Rest parameter canot be combined with default values", lexer.yyline());
      }

      update.param_types = new int[paramTypes.size()];
      for (int p = 0; p < paramTypes.size(); p++) {
         update.param_types[p] = (int) (long) paramTypes.get(p);
      }
      update.optional = (ValueKind[]) optionalValues.toArray(new ValueKind[optionalValues.size()]);
      update.unsetFlagHas_optional();
      if (!optionalValues.isEmpty()) {
         update.setFlagHas_optional();
      }
      update.unsetFlagNeed_rest();
      if (needsRest) {
         update.setFlagNeed_rest();
      }

      update.unsetFlagHas_paramnames();
      update.paramNames = new int[]{};
      boolean useParamNames = false;
      for (int p = 0; p < paramNames.size(); p++) {
         if (!paramNames.get(p).equals("param" + (p + 1))) {
            useParamNames = true;
         }
      }
      if (!Main.PARAM_NAMES_ENABLE) {
         useParamNames = false;
      }
      if (useParamNames) {
         update.setFlagHas_paramnames();
         update.paramNames = new int[paramNames.size()];
         for (int p = 0; p < paramNames.size(); p++) {
            update.paramNames[p] = abc.constants.forceGetStringId(paramNames.get(p));
         }
      }
      return true;

   }
}
